﻿using System;
using System.Collections;
using System.IO;
using System.Diagnostics;
using System.Threading;
using System.Windows.Forms;

namespace crontab
{
    public class Crond
    {
        /// <summary>
        /// Crontab 任务列表
        /// </summary>
        private static ArrayList crontab;
        /// <summary>
        /// 进程列表
        /// </summary>
        private static ArrayList processes;

        /// <summary>
        /// 最近一次执行的分钟
        /// </summary>
        private static int lastMinute;

        /// <summary>
        /// 进程超时：秒
        /// </summary>
        private static int taskTimeout = 1200;

        /// <summary>
        /// 是否终止线程
        /// </summary>
        private static bool letThreadAborting = false;

        /// <summary>
        /// 线程是否在运行
        /// </summary>
        private static bool threadIsRuning = false;
        private static bool threadIsStoping = false;

        private static int runInterval = 0;
        private static string cronfile = "";

        private static Thread task;

        /// <summary>
        /// 设置错误
        /// </summary>
        /// <param name="error">错误信息</param>
        private static void setError(string error)
        {
            string logfile = Application.StartupPath + "\\crontab.log";
            StreamWriter fileWriter = new StreamWriter(logfile, true);
            fileWriter.WriteLine(DateTime.Now.ToString() + " " + error);
            fileWriter.Close();
        }

        /// <summary>
        ///  从文件中解释Crontab
        /// </summary>
        /// <param name="taskList">Crontab 文本，每个元素为一行</param>
        private static void ParseCrontabFromFile(string file)
        {
            char[] sepCol = new char[] { ' ', '\t' };
            try
            {
                StreamReader fileReader = new StreamReader(file);
                String line;
                while ((line = fileReader.ReadLine()) != null)
                {
                    line = line.Trim();
                    if (line.Length == 0 || line.StartsWith("#"))
                    {
                        continue;
                    }

                    string[] cols = line.Split(sepCol, 7);
                    if (cols.Length < 6)
                    {
                        setError("parse error (line too short): " + line);
                    }

                    ArrayList task = new ArrayList();
                    task.Add(ParseToIntValues(cols[0], 0, 59)); // minutes
                    task.Add(ParseToIntValues(cols[1], 0, 23)); // hours
                    task.Add(ParseToIntValues(cols[2], 1, 31)); // day of month
                    task.Add(ParseToIntValues(cols[3], 1, 12)); // months
                    task.Add(ParseToIntValues(cols[4], 0, 6)); // day of week
                    task.Add(cols[5]);
                    task.Add(cols.Length > 6 ? cols[6] : "");

                    crontab.Add(task);
                }
                fileReader.Close();
            }
            catch (Exception e)
            {
                setError(e.Message);
            }
        }

        /// <summary>
        /// 解释时间到一个ArrayList
        /// </summary>
        /// <param name="input"></param>
        /// <param name="min"></param>
        /// <param name="max"></param>
        /// <returns></returns>
        private static ArrayList ParseToIntValues(string input, int min, int max)
        {
            // 保存分钟、小时、月、日、星期的时间值，如0,1,...,59
            ArrayList vals = new ArrayList();
            // 逗号分隔符
            char[] sepComma = new char[] { ',' };
            // 破折号与除号分隔符
            char[] sepPerDash = new char[] { '-', '/' };

            // 逗号列表
            string[] commaList = input.Split(sepComma);

            // 解释逗号列表
            foreach (string part in commaList)
            {
                int from, to, interval;

                string[] parts = part.Split(sepPerDash);
                int partsLength = parts.Length;

                if (parts[0].Equals("*"))
                {
                    // 解释 *、*/2 这两种格式
                    from = min;
                    to = max;
                    // 如果只有*号，则增长间隔为1，否则，增长间隔为第二部分的整数
                    interval = partsLength > 1 ? int.Parse(parts[1]) : 1;
                }
                else
                {
                    // 解释 2、2-8、2-8/2 这三种格式
                    from = int.Parse(parts[0]);
                    to = int.Parse(partsLength > 1 ? parts[1] : parts[0]);
                    interval = partsLength > 2 ? int.Parse(parts[2]) : 1;
                }
                if (interval < 1)
                {
                    continue;
                }

                for (int i = from; i <= to; i += interval)
                {
                    if (!vals.Contains(i))
                    {
                        vals.Add(i);
                    }
                }
            }

            return vals;
        }

        /// <summary>
        /// 轮询Crontab并执行任务
        /// </summary>
        /// <param name="now"></param>
        private static void DoCrontab(DateTime now)
        {
            if (now.Minute.Equals(lastMinute))
            {
                return;
            }

            int minuteStart = lastMinute == 59 ? 0 : lastMinute + 1;
            for (int minute = minuteStart; minute <= now.Minute; minute++)
            {
                foreach (ArrayList task in crontab)
                {
                    bool in_minute = ((ArrayList)task[0]).Contains(now.Minute);
                    bool in_hour = ((ArrayList)task[1]).Contains(now.Hour);
                    bool in_day = ((ArrayList)task[2]).Contains(now.Day);
                    bool in_month = ((ArrayList)task[3]).Contains(now.Month);
                    bool in_wday = ((ArrayList)task[4]).Contains((int)now.DayOfWeek);
                    if (in_minute && in_hour && in_day && in_month && in_wday)
                    {
                        Process proc = new Process();
                        proc.StartInfo.FileName = (string)task[5];
                        proc.StartInfo.Arguments = (string)task[6];

                        if (proc.Start())
                        {
                            processes.Add(proc);
                        }
                        else
                        {
                            setError("Could not start " + proc.StartInfo.FileName);
                        }
                    }
                }
            }

            lastMinute = now.Minute;
        }

        /// <summary>
        /// 检查进程，若有超时进程则Kill
        /// </summary>
        /// <param name="now"></param>
        private static void CheckProcesses(DateTime now)
        {
            ArrayList toRemove = new ArrayList();

            for (int i = 0; i < processes.Count; i++)
            {
                Process proc = (Process)processes[i];

                if (proc.HasExited)
                {
                    toRemove.Add(proc);

                    if (proc.ExitCode != 0)
                    {
                        setError("The process " + proc.StartInfo.FileName + " " +
                            proc.StartInfo.Arguments + " returned with error " + proc.ExitCode.ToString());
                    }
                }
                else if (DateTime.Compare(proc.StartTime, DateTime.Now.Subtract(new TimeSpan(0, 0, taskTimeout))) < 0)
                {
                    setError(proc.StartInfo.FileName + " takes longer than " + taskTimeout + " seconds and will be killed.");
                    proc.Kill();
                }
            }

            for (int i = toRemove.Count - 1; i >= 0; i--)
            {
                processes.Remove(toRemove[i]);
            }
        }

        /// <summary>
        /// 开始执行
        /// </summary>
        private static void Run()
        {
            DateTime now;
            crontab = new ArrayList();
            processes = new ArrayList();
            if (runInterval == 0)
            {
                runInterval = 30;
            }
            if (cronfile == "")
            {
                cronfile = Application.StartupPath + "\\crontab.list";
            }
            try
            {
                ParseCrontabFromFile(cronfile);
                lastMinute = DateTime.Now.Minute - 1;
                if (lastMinute < 0)
                {
                    lastMinute = 59;
                }

                threadIsRuning = true;
                while (!letThreadAborting)
                {
                    Thread.Sleep(runInterval * 1000); // sleep runInterval seconds
                    now = DateTime.Now;
                    CheckProcesses(now);
                    DoCrontab(now);
                }
                threadIsRuning = false;
            }
            catch (Exception e)
            {
                setError(e.Message);
            }
        }

        public static void SetRunInterval(int interval)
        {
            runInterval = interval;
        }

        public static void SetCrontabFile(string file)
        {
            cronfile = file;
        }

        /// <summary>
        /// 开始执行
        /// </summary>
        public static void start()
        {
            if (!threadIsStoping)
            {
                if (threadIsRuning)
                {
                    stop();
                }
                letThreadAborting = false;
                task = new Thread(new ThreadStart(Run));
                task.Start();
            }
        }

        public static void stop()
        {
            letThreadAborting = true;
            threadIsStoping = true;
            crontab.Clear();
            if (task != null)
            {
                task.Abort();
            }
            while (processes.Count > 0)
            {
                CheckProcesses(DateTime.Now);
                Thread.Sleep(1000); // sleep runInterval seconds
            }
            threadIsStoping = false;
        }

    }
}
